package com.eobard.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.github.javaparser.ParseProblemException;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.PackageDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import com.github.javaparser.ast.nodeTypes.NodeWithName;
import com.github.javaparser.ast.type.PrimitiveType;
import com.github.javaparser.resolution.TypeSolver;
import com.github.javaparser.resolution.types.ResolvedReferenceType;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.StreamUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Author: Eobard Gu
 * @Date: 2025-06-13
 * @Description: 混淆工具类
 */
@Slf4j
public class ObfuscatorUtils {

    //通过文件夹名称获取对于java或者xml文件引用的访问值,eg:values文件夹里面的文件对于java或者xml引用就会变成@/string或者R.string
    private static Map<String, String> fileNameReference = new HashMap<>();

    //所有Java基本、包装类型
    public static final List<String> allBasicPackagingType = getAllBasicPackagingType();


    static {
        //初始化xml资源文件的文件名访问键值对,k为文件夹名称,v为用于代码中访问的字段名称
        fileNameReference.put("drawable", "drawable");
        fileNameReference.put("layout", "layout");
        fileNameReference.put("raw", "raw");
        fileNameReference.put("xml", "xml");
        fileNameReference.put("values", "string");
    }

    private ObfuscatorUtils() {
    }

    /**
     * 根据文件夹名称获取用于访问java或xml的字段名称
     *
     * @param dir
     * @return
     */
    public static String getResourceReferenceValue(String dir) {
        return fileNameReference.entrySet()
                .stream()
                .filter(item -> dir.startsWith(item.getKey()))  //必须以这个Key开头
                .map(Map.Entry::getValue)                       //获取对应的映射值数据
                .findFirst()
                .orElse(null);                            //默认返回空
    }

    /**
     * 获取BasePackage中所有的java文件并放入fileMap中
     *
     * @param fileMap
     * @param sourceRoot
     */
    public static void loopFiles(Map<String, CompilationUnit> fileMap, String sourceRoot) {
        FileUtil.loopFiles(sourceRoot, pathname -> pathname.getName().endsWith(".java")).forEach(p -> {
            try {
                fileMap.put(p.toString(), StaticJavaParser.parse(p));
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * 获取模版里的静态函数
     *
     * @param staticMethodsFilePath:静态函数所在路径位置
     * @param tempStaticMethods:静态函数方法集合
     */
    public static void getStaticTemplateMethods(String staticMethodsFilePath, List<MethodDeclaration> tempStaticMethods) {
        URL staticMethodsFile = ObfuscatorUtils.class.getResource(staticMethodsFilePath);
        if (null != staticMethodsFile) {
            try {
                CompilationUnit cu = StaticJavaParser.parse(Paths.get(staticMethodsFile.getFile()));
                cu.getPrimaryTypeName().flatMap(cu::getClassByName).ifPresent(cid -> tempStaticMethods.addAll(cid.getMethods()));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    public static List<MethodDeclaration> getStaticTemplateMethods(String staticMethodsFilePath) {
        List<MethodDeclaration> tempStaticMethods = new ArrayList<>();
        try {
            // 1.加载静态资源文件
            Resource resource = new PathMatchingResourcePatternResolver().getResource("classpath:" + staticMethodsFilePath);
            // 2. 复制内容
            String content = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);

            // 3. 解析静态资源源码
            CompilationUnit cu = StaticJavaParser.parse(content);

            // 4. 获取方法列表
            cu.findAll(MethodDeclaration.class).stream()
                    .filter(MethodDeclaration::isStatic)
                    .forEach(tempStaticMethods::add);

        } catch (IOException | ParseProblemException e) {
            log.error("failed to parse static methods from {}", staticMethodsFilePath, e);
        }
        return tempStaticMethods;
    }


    /**
     * 收集fileMap中必须是packageList包中文件的K
     *
     * @param fileMap:sourceRoot中所有文件的集合
     * @param packageList:指定需要混淆的包列表
     * @return:filemap中符合指定混淆包列表的所有K值
     */
    public static List<String> collectBelongsToPackageCLassFileKey(Map<String, CompilationUnit> fileMap, List<String> packageList) {
        assert CollectionUtil.isNotEmpty(fileMap) && CollectionUtil.isNotEmpty(packageList);
        return fileMap.entrySet()
                .stream()
                //过滤出当前CU类所在的包在传入包名集合packageList的实体类
                .filter(item -> packageList.stream()
                        .anyMatch(pkg ->
                                //获取当前CU类的实体类信息
                                item.getValue()
                                        //获取包名
                                        .getPackageDeclaration()
                                        //如果以packageList中任何一个以pkg开头的则满足条件
                                        .map(pd -> pd.getNameAsString().startsWith(pkg))
                                        .orElse(false))).map(Map.Entry::getKey).collect(Collectors.toList());
    }


    /**
     * 获取fileMap中指定key的包名称
     *
     * @param fileMap:sourceRoot中所有文件的集合
     * @param fileMapKey:fileMap中哪些key
     * @param needFormat:是否需要转换包名称的格式,eg: com/eobard/domain or com.eobard.domain
     * @return:fileMap中指定key中java文件的所属包名
     */
    public static List<String> getFilePackageNameByFileMapKey(Map<String, CompilationUnit> fileMap, List<String> fileMapKey, boolean needFormat) {
        if (CollectionUtil.isEmpty(fileMap) || CollectionUtil.isEmpty(fileMapKey))
            throw new RuntimeException("请检查包信息是否正确");

        return fileMapKey.stream()
                .map(key -> {
                    String pkgName = fileMap.get(key)
                            .getPackageDeclaration()
                            .get().getNameAsString();

                    return needFormat ? pkgName.replace(".", "/") : pkgName;
                })
                .collect(Collectors.toList());
    }


    /**
     * 收集属于指定包中的CompilationUnit文件
     *
     * @param fileMap:sourceRoot中所有文件的集合
     * @param packageList:指定需要混淆的包列表
     * @param otherCondition:其它过滤条件,可为空
     * @return:根据条件过滤后的CompilationUnit文件
     */
    public static List<CompilationUnit> collectBelongsToPackageCLassFile(Map<String, CompilationUnit> fileMap, List<String> packageList,
                                                                         Predicate<Map.Entry<String, CompilationUnit>> otherCondition) {
        assert CollectionUtil.isNotEmpty(fileMap) && CollectionUtil.isNotEmpty(packageList);
        //主要收集fileMap中必须是对应包中的文件
        Stream<Map.Entry<String, CompilationUnit>> entryStream = fileMap.entrySet()
                .stream()
                //过滤出当前CU类所在的包在传入包名集合packageList的实体类
                .filter(item -> packageList.stream()
                        .anyMatch(pkg ->
                                //获取当前CU类的实体类信息
                                item.getValue()
                                        //获取包名
                                        .getPackageDeclaration()
                                        //如果以packageList中任何一个以pkg开头的则满足条件
                                        .map(pd -> pd.getNameAsString().startsWith(pkg))
                                        .orElse(false)));
        //如果有其它条件则加上判断
        if (ObjectUtil.isNotEmpty(otherCondition)) {
            entryStream = entryStream.filter(otherCondition);

        }
        return entryStream
                //满足条件的话就吧这些类的文件收集起来
                .map(Map.Entry::getValue)
                .collect(Collectors.toList());
    }

    /**
     * 判断当前node节点是否是指定targetClazz的类型
     *
     * @param node
     * @param targetClazz
     * @return:boolean
     */
    public static boolean hasParentNode(Optional<Node> node, Class targetClazz) {
        return node.isPresent() && targetClazz.isInstance(node.get());
    }


    /**
     * 获取当前CompilationUnit中的类名字符串
     *
     * @param cu
     * @param needFullyQualifiedName:true返回包名.类名字符串,false返回类名字符串
     * @return:com.xx.A或A
     */
    public static String getClassName(CompilationUnit cu, boolean needFullyQualifiedName) {
        String className = cu.getTypes()
                .stream()
                .filter(item -> item instanceof ClassOrInterfaceDeclaration)
                .findFirst()
                .map(type -> {
                    if (needFullyQualifiedName) return type.getFullyQualifiedName().orElse(null);
                    return type.getNameAsString();
                }).orElse(null);

        if (className == null || StrUtil.isBlank(className)) {
            String simpleName = cu.getPrimaryTypeName().orElseThrow();
            if (needFullyQualifiedName) {
                String pkg = cu.getPackageDeclaration()
                        .map(NodeWithName::getNameAsString)
                        .map(p -> p + ".")
                        .orElse("");
                className = pkg + simpleName;
            } else {
                className = simpleName;
            }
        }
        return className;
    }


    /**
     * 获取当前CompilationUnit中的类名字符串
     *
     * @param cu
     * @return A
     */
    public static String getClassName(CompilationUnit cu) {
        return cu.getPrimaryTypeName().orElseThrow();
    }

    /**
     * 获取当前CompilationUnit中指定clazzName的所属包名
     *
     * @param cu
     * @param clazzName
     * @return:com.example.utils.InternetUtils
     */
    public static String getCLassNamePackageInfo(CompilationUnit cu, String clazzName) {
        //从当前类的导入包中寻找
        for (ImportDeclaration imp : cu.getImports()) {
            //获取导入包的字符串
            String impName = imp.getNameAsString();
            //是否以import static xxx.Xxx这种形式导入了常量字段
            boolean isStatic = imp.isStatic();

            if (!isStatic && impName.endsWith("." + clazzName)) {
                return impName;
            } else if (isStatic && impName.contains("." + clazzName)) {
                String className = impName.substring(0, impName.lastIndexOf("."));
                if (className.endsWith(clazzName)) {
                    return className;
                }
            }
        }
        //如果没有从import导入，说明是当前类里面的静态常量字段，直接获取当前类的包名
        Optional<PackageDeclaration> pkg = cu.getPackageDeclaration();
        return pkg.map(packageDeclaration -> packageDeclaration.getNameAsString() + "." + clazzName).orElse(null);
    }

    /**
     * 通过类型求解器,从AST中解析出表达式的引用类型,获取当前CompilationUnit中指定字段的所属包名
     *
     * @param typeSolver:TypeSolver实例，eg：BaseObfuscator类中可把CombinedTypeSolver solver对象抽取成全局static对象
     * @param n
     * @return:com.example.utils.InternetUtils
     */
    public static String getFieldPackageInfo(TypeSolver typeSolver, FieldAccessExpr n) {
        try {
            ResolvedType type = JavaParserFacade.get(typeSolver).getType(n.getScope());
            if (type.isReferenceType()) {
                ResolvedReferenceType refType = type.asReferenceType();
                return refType.getQualifiedName();
            }
        } catch (Exception e) {
        }
        return null;
    }

    /**
     * 保存混淆后的代码到文件中
     *
     * @param fileMap
     */
    public static void saveObfuscatedFiles(Map<String, CompilationUnit> fileMap) {
        fileMap.forEach((path, cu) -> {
            try {
                Files.writeString(Paths.get(path), cu.toString());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
    }

    /**
     * 获取Java所有的基本、包装类型:[boolean, char, byte, short, int, long, float, double, String, Boolean, Character, Byte, Short, Integer, Long, Float, Double]
     *
     * @return
     */
    private static List<String> getAllBasicPackagingType() {
        List<String> basicType = List.of("boolean", "char", "byte", "short", "int", "long", "float", "double");
        //所有基本、包装类型
        List<String> allType = new ArrayList<>();
        allType.addAll(basicType);
        allType.add("String");
        try {
            //通过反射获取java parser的枚举类型
            Field f = PrimitiveType.Primitive.class.getDeclaredField("nameOfBoxedType");
            f.setAccessible(true);
            //遍历所有的枚举值
            Arrays.asList(PrimitiveType.Primitive.values()).forEach(p -> {
                try {
                    allType.add((String) f.get(p));
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return allType;
    }

    //获取root下装载的java文件
    public static List<String> scanJavaClassNames(String root) {
        List<String> classNames = new ArrayList<>();
        try {
            Files.walk(Paths.get(root))
                    .filter(path -> path.toString().endsWith(".java"))
                    .forEach(path -> {
                        try {
                            String relative = Paths.get(root).relativize(path).toString();
                            String className = relative
                                    .replace(File.separator, ".")
                                    .replaceAll("\\.java$", "");
                            classNames.add(className);
                        } catch (Exception ignored) {
                        }
                    });
        } catch (IOException e) {
        }
        return classNames;
    }

    /**
     * 规范化包名：如果包是目录结构则转为dot结构
     * <ul>
     *     <li>eg：com/eobard/utils   ->   com.eobard.utils</li>
     *     <li>eg：com.eobard.utils.  ->   com.eobard.utils</li>
     *     <li>eg：com.eobard/utils   ->   com.eobard.utils</li>
     *     <li>eg：com/eobard/utils.  ->   com.eobard.utils</li>
     * </ul>
     *
     * @param packages
     */
    public static Set<String> normalizedPackage(Collection<String> packages) {
        if (CollectionUtil.isEmpty(packages)) return Collections.EMPTY_SET;
        return packages.stream()
                .map(s -> {
                    //去除包中的多余空格
                    String t = s.trim();
                    //判断包中是否含有多余的dot,eg：com.eobard.utils.
                    if (t.endsWith(".")) t = t.substring(0, t.length() - 1);
                    //判断包是否属于目录结构的
                    if (t.contains(File.separator)) t = t.replaceAll(File.separator, ".");
                    return t;
                })
                .collect(Collectors.toSet());
    }

}
